package com.ktwlrj.logmanage.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ktwlrj.core.base.DetailEntity;
import com.ktwlrj.core.util.ContextHolderUtils;
import com.ktwlrj.core.util.IPUtils;
import com.ktwlrj.core.util.KtwlStringUtils;
import com.ktwlrj.core.util.ServletUtils;
import com.ktwlrj.logmanage.annotation.SysLog;
import com.ktwlrj.logmanage.entity.LogEntity;
import com.ktwlrj.logmanage.service.LogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 日志切面
 *
 * @author zhouxuanhong
 * @date 2019-04-02 16:50
 * @since JDK1.8
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LogAspect {
    private static final String REQUEST_JSON_TYPE = "application";
    private final LogService service;
    private final ContextHolderUtils contextHolderUtils;

    /**
     * 切点使用注解形式
     *
     * @author zhouxuanhong
     * @date 2019-04-02 16:56
     */
    @Pointcut("@annotation(com.ktwlrj.logmanage.annotation.SysLog)")
    public void logPointCut() {

    }


    /**
     * 环绕 @Around
     *
     * @param point ${@link ProceedingJoinPoint}
     * @return Object ${@link Object}
     * @author zhouxuanhong
     * @date 2019-04-02 17:00
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 开始时间
        long startTime = System.currentTimeMillis();
        Object result;
        // 这里需要捕获异常，避免因为异常导致部分日志数据没有入库
        try {
            result = point.proceed();
        } catch (Exception e) {
            // 异常时的结束时间
            long endTime = System.currentTimeMillis();
            // 当前日志入库
            getSysLog(point, startTime, endTime, true, e.getMessage());
            // 日志入库后异常需要抛出去交给统一异常处理类来处理
            throw e;
        }
        // 结束时间
        long endTime = System.currentTimeMillis();
        // 当前日志入库
        getSysLog(point, startTime, endTime, false, null);
        return result;
    }


    /**
     * 日志入库前处理方法
     *
     * @param joinPoint    ${@link ProceedingJoinPoint}
     * @param startTime    ${@link Long} 执行开始时间
     * @param endTime      ${@link Long} 执行结束时间
     * @param isException  ${@link Boolean} 是否发生异常
     * @param exceptionMsg ${@link String} 异常信息
     * @author zhouxuanhong
     * @date 2019-06-20 14:24
     */
    private void getSysLog(ProceedingJoinPoint joinPoint, Long startTime, Long endTime, boolean isException, String exceptionMsg) {
        DetailEntity detailEntity = contextHolderUtils.getDetailEntity();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLog syslog = method.getAnnotation(SysLog.class);
        // 请求ip
        LogEntity logEntity = new LogEntity();
        logEntity.setDeleteMark(0);
        logEntity.setExceptionState(0);
        logEntity.setCreateTime(new Date());
        logEntity.setRequestIp(IPUtils.getIpAddr());
        // 请求执行时间
        Map<String, Object> requestInfo = new HashMap<>(10);
        requestInfo.put("startTime", startTime);
        requestInfo.put("endTime", endTime);
        requestInfo.put("time", endTime - startTime);
        // 注解上操作描述与日志操作类型
        if (syslog != null) {
            requestInfo.put("operation", syslog.value());
            logEntity.setLogType(syslog.handleType());
        }
        // 请求方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        requestInfo.put("methodInfo", className + "." + methodName + "()");
        // 请求执行结果
        logEntity.setRequestState(isException ? 1 : 0);
        // 请求失败异常信息
        if (isException) {
            requestInfo.put("exception", exceptionMsg);
        }
        // 请求响应状态
        HttpServletResponse response = ServletUtils.getResponse();
        if (response != null) {
            requestInfo.put("status", response.getStatus());
        }
        logEntity.setLogBody(JSON.toJSONString(requestInfo));
        // 请求方法调用参数(排除文件流)
        try {
            logEntity.setMethodData(getMethodParams(signature, joinPoint));
        } catch (Exception e) {
            log.debug("----------LogAspect---------->getSysLog:{}", e.getMessage());
        }
        logEntity.setLogId(KtwlStringUtils.getUuId());
        String realName = "unknown";
        String memberId = "unknown";
        if (detailEntity != null) {
            realName = detailEntity.getRealName();
            memberId = detailEntity.getMemberId();
        }
        logEntity.setRealName(realName);
        logEntity.setCreateUser(memberId);
        // 判断当前请求是否正常(不包含会员Id则不正常)
        logEntity.setAlreadyUsed(0);
        if (StringUtils.isNotBlank(logEntity.getCreateUser())) {
            logEntity.setAlreadyUsed(1);
        }
        // 日志异步存储
        log.debug("----------LogAspect---------->{}", logEntity);
        service.saveLog(logEntity);
    }


    /**
     * 获取方法请求参数
     *
     * @param methodSignature ${@link MethodSignature}
     * @param joinPoint       ${@link ProceedingJoinPoint}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-06-20 16:33
     */
    private String getMethodParams(MethodSignature methodSignature, ProceedingJoinPoint joinPoint) {
        String params = "";
        JSONObject jsonObject = new JSONObject();
        HttpServletRequest request = ServletUtils.getRequest();
        if (request != null) {
            // 请求参数处理
            String contentType = request.getContentType();
            if (StringUtils.isBlank(contentType) || contentType.contains(REQUEST_JSON_TYPE)) {
                String[] parameterNames = methodSignature.getParameterNames();
                Object[] args = joinPoint.getArgs();
                int len = parameterNames.length;
                if (args != null && parameterNames.length == args.length) {
                    for (int i = 0; i < len; i++) {
                        Object objectParam = args[i];
                        if (objectParam instanceof BeanPropertyBindingResult) {
                            continue;
                        }
                        if (objectParam instanceof HttpServletRequest) {
                            continue;
                        }
                        if (objectParam instanceof HttpServletResponse) {
                            continue;
                        }
                        try {
                            jsonObject.put(parameterNames[i], JSONObject.toJSON(objectParam));
                        } catch (Exception e) {
                            log.debug("----------LogAspect---------->getMethodParams:{}", "日志记录获取方法参数转换json异常");
                            if (objectParam != null) {
                                jsonObject.put(parameterNames[i], objectParam.toString());
                            }
                        }
                    }
                }
                params = jsonObject.toJSONString();
                // 去掉参数中有关密码关键字的字符串
                params = params.replaceAll("\"password\":\"[0-9a-zA-Z]+\"", "\"password\":\"******\"");
                params = params.replaceAll("\"newPassword\":\"[0-9a-zA-Z]+\"", "\"newPassword\":\"******\"");
                params = params.replaceAll("\"oldPassword\":\"[0-9a-zA-Z]+\"", "\"oldPassword\":\"******\"");
                params = params.replaceAll("\"Password\":\"[0-9a-zA-Z]+\"", "\"Password\":\"******\"");
                params = params.replaceAll("\"NewPassword\":\"[0-9a-zA-Z]+\"", "\"NewPassword\":\"******\"");
                params = params.replaceAll("\"OldPassword\":\"[0-9a-zA-Z]+\"", "\"OldPassword\":\"******\"");
                params = params.replaceAll("\"mobilePhone\":\"[0-9a-zA-Z]+\"", "\"mobilePhone\":\"******\"");
                params = params.replaceAll("\"phone\":\"[0-9a-zA-Z]+\"", "\"phone\":\"******\"");
                params = params.replaceAll("\"idCard\":\"[0-9a-zA-Z]+\"", "\"idCard\":\"******\"");
                params = params.replaceAll("\"certificationId\":\"[0-9a-zA-Z]+\"", "\"certificationId\":\"******\"");
                params = params.replaceAll("\"corporateCertificationPhone\":\"[0-9a-zA-Z]+\"", "\"corporateCertificationPhone\":\"******\"");
                params = params.replaceAll("\"idcard\":\"[0-9a-zA-Z]+\"", "\"idcard\":\"******\"");

            }
        }
        return params;
    }
}
